package core;

import java.util.*;
import java.awt.Dimension;
import java.io.*;

import javax.swing.JFrame;
import javax.swing.JScrollPane;

/**
 * A SongRecord stores description fields about a song, its filename,
 * and its lyrics (in stanzas, as blocks). It can read/write song data
 * from/to a file.
 * 
 * @author john
 *
 */
public class SongRecord extends Record implements Presentable {
	
	private static final long serialVersionUID = 2484233081990050017L;
	public static final String[] fieldNames = 
		{"Title", "Author", "Copyright", "Key", "Slide Order", "Tags", "Background Image"};
	public static final String[] elementNames = 
		{"TITLE", "AUTHOR", "COPYRIGHT", "KEY", "SLIDEORDER", "TAGS", "BGIMG"};
	public static final int TITLE = 0,
								AUTHOR = 1,
								COPYRIGHT = 2,
								KEY = 3,
								SLIDE_ORDER = 4,
								TAGS = 5,
								BG_IMG = 6;
	public static final int NUM_FIELDS = 7;
	
	private String[] fields;
	private File datafile;
	//private transient String lyrics;
	private transient ArrayList<WTagBlock> lyrics;
	
	/**
	 * Creates a new SongRecord with a default filename in the given
	 * parent directory.
	 * @param id			unique song ID number
	 * @param title			song title
	 * @param parentDir		directory to save song in
	 */
	public SongRecord(int id, String title, String parentDir) {
		this(id, createFile(title, parentDir, "txt"));
		fields[TITLE] = title;
	}
	
	/**
	 * Creates new or loads existing SongRecord with the given filename.
	 * @param id		unique song ID number
	 * @param datafile	file to save song to
	 */
	public SongRecord(int id, File file) {
		super(id);
		this.datafile = file;
		fields = new String[NUM_FIELDS];
		lyrics = new ArrayList<WTagBlock>();
		if (datafile.exists())
			load();
		else
			try {
				datafile.createNewFile();
			} catch (IOException e) {
				e.printStackTrace();
			}
	}
	
	// Serialization methods
	private void writeObject(ObjectOutputStream out) throws IOException {
		out.writeObject(fields);
		out.writeObject(datafile);
	}
	private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
		fields = (String[])in.readObject();
		datafile = (File)in.readObject();
	}
	
	/**
	 * Parses the input string, recognizing and updating the data fields,
	 * and setting the rest as the lyrics.
	 * @param str
	 */
	public void setLyrics(String str) {
		parse(str);
		save();
	}
	public String getLyrics() {
		if (lyrics == null)
			load();
		
		StringBuilder sb = new StringBuilder();
		for (WTagBlock b : lyrics) {
			sb.append(b);
			sb.append("\n\n");
		}
		return sb.toString().trim();
	}
	public List<WTagBlock> getLyricsBlocks() {
		if (lyrics == null)
			load();
		return lyrics;
	}
	
	public void setField(int i, Object value) {
		fields[i] = (String)value;
	}
	public Object getField(int i) {
		return fields[i];
	}
	
	// Presentable interface
	public WTagBlock getSlide(int i) {
		if (lyrics == null)
			load();
		return lyrics.get(i);
	}
	public int getSlideCount() {
		if (lyrics == null)
			load();
		return lyrics.size();
	}
	public String getTitle() {
		return fields[TITLE];
	}
	
	
	public String toString() {
		return "SongRecord id:" + getId() + " " + Arrays.toString(fields);
	}
	
	/**
	 * Tests whether the datafiles of the two records are equal.
	 */
	public boolean equals(Object obj) {
		return (obj instanceof SongRecord &&
				((SongRecord)obj).datafile.equals(this.datafile));
	}
	
	private void parse(String str) {
		// get a lock on fields and lyrics to prevent other threads
		//  (eg. graphical components) from getting corrupt data
		synchronized(this) {
			WTagBlock[] blocks = WTagFormat.parse(str);
			lyrics = new ArrayList<WTagBlock>();
			
			for(WTagBlock b : blocks) {
				if(isField(b.getElement()))
					setField(getIndex(b.getElement()), b.getAttribute());
				else
					lyrics.add(b);
			}
		}
	}
	
	private void load() {
		try {
			BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(datafile), "UTF8"));
			StringBuilder sb = new StringBuilder();
			int i;
			
			while ((i = in.read()) != -1)
				sb.append((char)i);
			in.close();
			parse(sb.toString());
			
		} catch (IOException ex) {
			ex.printStackTrace();
		} 
	}
	
	private void save() {
		try {
			PrintWriter out = new PrintWriter(new OutputStreamWriter(new FileOutputStream(datafile), "UTF8"));
			
			// print data fields
			for (int i=0; i<fields.length; i++)
				if (fields[i] != null)
					out.println("[" + elementNames[i] + " " + fields[i] + "]");
			out.println();
			out.print(getLyrics());
			out.close();
			
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	public boolean delete() {
		return datafile.delete();
	}
	
	/*
	 * Sanitizes a filename and returns a file pointer with the given filename.
	 * If the file already exists, it adds a number to the end of the filename
	 * to make it unique.
	 */
	private static File createFile(String filename, String directory, String extension) {
		
		// clean up filenames
		extension = extension.trim();
		directory = directory.trim();
		filename = sanitizeFilename(filename);
		if (directory.charAt(directory.length() - 1) != File.separatorChar)
			directory += File.separator;
		if (filename.length() == 0)
			filename = "song";
		
		File file = new File(directory + filename + "." + extension);
		try {
			int i = 1;
			// try to create the file
			while (!file.createNewFile()) {
				// if it already exists, try to create a file with a different name
				file = new File(directory + filename + Integer.toString(i) + "." + extension);
				i++;
			}
		} catch (IOException ex) {
			ex.printStackTrace();
		}
		return file;
	}
	private static String sanitizeFilename(String name) {
		char[] ary = name.trim().toCharArray();
		for (int i=0; i<ary.length; i++)
			if (!isLegalFilenameCharacter(ary[i]))
				ary[i] = '_';
		return new String(ary);
	}
	private static boolean isLegalFilenameCharacter(char c) {
		final String illegals = "\\/:*?\"<>| ";
		for (int i=0; i<illegals.length(); i++)
			if (c == illegals.charAt(i))
				return false;
		return true;
	}
	
	/**
	 * Tests whether the string is the name of a data field. 
	 */
	public static boolean isField(String name) {
		for(int i=0; i<elementNames.length; i++)
			if (elementNames[i].equalsIgnoreCase(name))
				return true;
		return false;
	}
	/**
	 * Finds the index of the data field with that element name.
	 * Returns index, or -1 if not a field.
	 */
	public static int getIndex(String field) {
		for(int i=0; i<elementNames.length; i++)
			if (elementNames[i].equalsIgnoreCase(field))
				return i;
		return -1;
	}
	
	public static void main(String[] args) throws FileNotFoundException {
		//SongRecordTag tag = readTag("[asdf ][title Hi my name is bob]");
		//System.out.println(tag.element + "(" + tag.isField + "): " + tag.attribute);
		
		SongRecord sr = new SongRecord(0, new File("data/data.txt"));
		sr.load();
		System.out.println(sr);
		System.out.println(sr.getLyrics());
		sr.save();
		
		JFrame fr = new JFrame();
		fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		SongEditor se = new SongEditor();
		se.setText(sr.getLyrics());
		JScrollPane sp = new JScrollPane(se);
		sp.setPreferredSize(new Dimension(300, 200));
		fr.getContentPane().add(sp);
		fr.pack();
		fr.setVisible(true);
		
		//ui.PresentationDisplay pd = new ui.PresentationDisplay(0);
		//pd.setMainText(sr.getLyrics());
		
		/*JFrame fr = new JFrame();
		fr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		fr.getContentPane().add(new JLabel(sr.getLyrics()));
		fr.pack();
		fr.setVisible(true);*/
		
		//System.out.println(sr.lyrics.get(sr.lyrics.keySet().iterator().next()));
		
//		try {
//			System.out.println(Arrays.toString((String[])(new SongRecord(0, "blah", "data/").getClass().getField("fieldNames").get(null))));
//		} catch (IllegalAccessException e) {
//			e.printStackTrace();
//		} catch (NoSuchFieldException e) {
//			e.printStackTrace();
//		}
	}
	
	/* ---DEPRECATED parser code---
	private void parse(String str) {
		boolean inBlock = false, inTag = false;
		StringBuilder tag = new StringBuilder();
		StringBuilder block = new StringBuilder();
		char c;
		
		for (int i=0; i<str.length(); i++) {
				
			switch (c = str.charAt(i)) {
			case '[':
				// if an open bracket is encountered within a tag, 
				//  discard that tag and start over
				if (inTag) {
					inTag = false;
					System.err.println("Misformed tag encountered: " + tag.toString());
					tag = new StringBuilder();
				}
				
				// if we were previously reading a block of text,
				//  stop and store it to lyrics
				if (inBlock) {
					lyrics += block.toString().trim() + "\n\n";
					block = new StringBuilder();
					inBlock = false;
				}
				// start reading a tag
				inTag = true;
				tag.append(c);
				break;
			
			case ']':
				if (inTag) {
					inTag = false;
					tag.append(c);
					
					// parse the tag
					SongRecordTag t = new SongRecordTag(tag.toString());
					// if it is a data field, update it
					if (isField(t.element))
						setField(getIndex(t.element), t.attribute);
					// otherwise, it is a start of block
					else {
						block.append(t.toString());
						inBlock = true;
					}
					
					//System.out.println("tag read: " + tag.toString());
					tag = new StringBuilder();
				}
				else {
					System.err.println("Closing brace without opening encountered");
				}
				break;
				
			case '\r':
			case '\n':
				if(inTag) {
					inTag = false;
					System.err.println("Misformed tag encountered: " + tag.toString());
					tag = new StringBuilder();
					inBlock = true;
				}
				else if (inBlock)
					block.append(c);
				break;
			
			default:
				if(inTag)
					tag.append(c);
				else if(inBlock)
					block.append(c);
				break;
			}
		}
		
		lyrics += block.toString();
		lyrics = lyrics.trim();
	}
	*/
}

class SongRecordTag {
	String element;
	String attribute;
	
	/**
	 * Parses a song tag from a line of text.
	 * @param line
	 */
	public SongRecordTag(String line) {
		boolean inTag = false, elementRead = false;
		char c;
		StringBuilder elem = new StringBuilder(),
					attrib = new StringBuilder();
		
		for (int i=0; i<line.length(); i++)
			switch (c = line.charAt(i)) {
			
			case '[':
				if(!inTag) {
					inTag = true;
					elementRead = false;
				}
				break;
			
			case ']':
				inTag = false;
				break;
				
			case ' ':
				if(inTag && !elementRead)
					elementRead = true;
				else if(inTag)
					attrib.append(c);
				break;
			
			default:
				if(inTag && !elementRead)
					elem.append(c);
				else if(inTag)
					attrib.append(c);
				break;
			}
		
		element = elem.toString();
		attribute = attrib.toString();
		
		//System.out.println(element + ": " + attribute);
	}
	
	public String toString() {
		if (attribute.trim().equals(""))
			return "[" + element + "]";
		return "[" + element + " " + attribute + "]";
	}
}